/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.web.controller.user;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Person;
import org.openmrs.PersonName;
import org.openmrs.Role;
import org.openmrs.User;
import org.openmrs.api.PasswordException;
import org.openmrs.api.UserService;
import org.openmrs.api.context.Context;
import org.openmrs.messagesource.MessageSourceService;
import org.openmrs.propertyeditor.RoleEditor;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.util.PrivilegeConstants;
import org.openmrs.validator.UserValidator;
import org.openmrs.web.WebConstants;
import org.openmrs.web.user.UserProperties;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;

/**
 * Used for creating/editing User
 */
@Controller
public class UserFormController {
	
	protected static final Log log = LogFactory.getLog(UserFormController.class);
	
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Role.class, new RoleEditor());
	}
	
	// the personId attribute is called person_id so that spring MVC doesn't try to bind it to the personId property of user
	@ModelAttribute("user")
	public User formBackingObject(WebRequest request, @RequestParam(required = false, value = "person_id") Integer personId) {
		String userId = request.getParameter("userId");
		User u = null;
		try {
			u = Context.getUserService().getUser(Integer.valueOf(userId));
		}
		catch (Exception ex) {}
		if (u == null) {
			u = new User();
		}
		if (personId != null) {
			u.setPerson(Context.getPersonService().getPerson(personId));
		} else if (u.getPerson() == null) {
			Person p = new Person();
			p.addName(new PersonName());
			u.setPerson(p);
		}
		return u;
	}
	
	@ModelAttribute("allRoles")
	public List<Role> getRoles(WebRequest request) {
		List<Role> roles = Context.getUserService().getAllRoles();
		if (roles == null)
			roles = new Vector<Role>();
		
		for (String s : OpenmrsConstants.AUTO_ROLES()) {
			Role r = new Role(s);
			roles.remove(r);
		}
		return roles;
	}
	
	@RequestMapping(value = "/admin/users/user.form", method = RequestMethod.GET)
	public String showForm(@RequestParam(required = false, value = "userId") Integer userId,
	        @RequestParam(required = false, value = "createNewPerson") String createNewPerson,
	        @ModelAttribute("user") User user, ModelMap model) {
		
		// the formBackingObject method above sets up user, depending on userId and personId parameters   
		
		model.addAttribute("isNewUser", isNewUser(user));
		if (isNewUser(user) || Context.hasPrivilege(PrivilegeConstants.EDIT_USER_PASSWORDS))
			model.addAttribute("modifyPasswords", true);
		
		if (createNewPerson != null)
			model.addAttribute("createNewPerson", createNewPerson);
		
		if (!isNewUser(user))
			model.addAttribute("changePassword", new UserProperties(user.getUserProperties()).isSupposedToChangePassword());
		
		// not using the default view name because I'm converting from an existing form
		return "admin/users/userForm";
	}
	
	/**
	 * @should work for an example
	 */
	@RequestMapping(value = "/admin/users/user.form", method = RequestMethod.POST)
	public String handleSubmission(WebRequest request, HttpSession httpSession, ModelMap model,
	        @RequestParam(required = false, value = "action") String action,
	        @RequestParam(required = false, value = "userFormPassword") String password,
	        @RequestParam(required = false, value = "secretQuestion") String secretQuestion,
	        @RequestParam(required = false, value = "secretAnswer") String secretAnswer,
	        @RequestParam(required = false, value = "confirm") String confirm,
	        @RequestParam(required = false, value = "forcePassword") Boolean forcePassword,
	        @RequestParam(required = false, value = "roleStrings") String[] roles,
	        @RequestParam(required = false, value = "createNewPerson") String createNewPerson,
	        @ModelAttribute("user") User user, BindingResult errors) {
		
		UserService us = Context.getUserService();
		MessageSourceService mss = Context.getMessageSourceService();
		
		if (!Context.isAuthenticated()) {
			errors.reject("auth.invalid");
		} else if (mss.getMessage("User.assumeIdentity").equals(action)) {
			Context.becomeUser(user.getSystemId());
			httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.assumeIdentity.success");
			httpSession.setAttribute(WebConstants.OPENMRS_MSG_ARGS, user.getPersonName());
			return "redirect:/index.htm";
			
		} else if (mss.getMessage("User.delete").equals(action)) {
			try {
				Context.getUserService().purgeUser(user);
				httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.delete.success");
				return "redirect:users.list";
			}
			catch (Exception ex) {
				httpSession.setAttribute(WebConstants.OPENMRS_ERROR_ATTR, "User.delete.failure");
				log.error("Failed to delete user", ex);
				return "redirect:/admin/users/user.form?userId=" + request.getParameter("userId");
			}
			
		} else if (mss.getMessage("User.retire").equals(action)) {
			String retireReason = request.getParameter("retireReason");
			if (!(StringUtils.hasText(retireReason))) {
				errors.rejectValue("retireReason", "User.disableReason.empty");
				return showForm(user.getUserId(), createNewPerson, user, model);
			} else {
				us.retireUser(user, retireReason);
				httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.retiredMessage");
			}
			
		} else if (mss.getMessage("User.unRetire").equals(action)) {
			us.unretireUser(user);
			httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.unRetiredMessage");
		} else {
			
			// check if username is already in the database
			if (us.hasDuplicateUsername(user))
				errors.rejectValue("username", "error.username.taken");
			
			// check if password and password confirm are identical
			if (password == null || password.equals("XXXXXXXXXXXXXXX"))
				password = "";
			if (confirm == null || confirm.equals("XXXXXXXXXXXXXXX"))
				confirm = "";
			
			if (!password.equals(confirm))
				errors.reject("error.password.match");
			
			if (password.length() == 0 && isNewUser(user))
				errors.reject("error.password.weak");
			
			//check password strength
			if (password.length() > 0) {
				try {
					OpenmrsUtil.validatePassword(user.getUsername(), password, user.getSystemId());
				}
				catch (PasswordException e) {
					errors.reject(e.getMessage());
				}
			}
			
			Set<Role> newRoles = new HashSet<Role>();
			if (roles != null) {
				for (String r : roles) {
					// Make sure that if we already have a detached instance of this role in the
					// user's roles, that we don't fetch a second copy of that same role from
					// the database, or else hibernate will throw a NonUniqueObjectException.
					Role role = null;
					if (user.getRoles() != null)
						for (Role test : user.getRoles())
							if (test.getRole().equals(r))
								role = test;
					if (role == null) {
						role = us.getRole(r);
						user.addRole(role);
					}
					newRoles.add(role);
				}
			}
			
			if (user.getRoles() == null)
				newRoles.clear();
			else
				user.getRoles().retainAll(newRoles);
			
			String[] keys = request.getParameterValues("property");
			String[] values = request.getParameterValues("value");
			
			if (keys != null && values != null) {
				for (int x = 0; x < keys.length; x++) {
					String key = keys[x];
					String val = values[x];
					user.setUserProperty(key, val);
				}
			}
			
			new UserProperties(user.getUserProperties()).setSupposedToChangePassword(forcePassword);
			
			UserValidator uv = new UserValidator();
			uv.validate(user, errors);
			
			if (errors.hasErrors()) {
				return showForm(user.getUserId(), createNewPerson, user, model);
			}
			
			if (isNewUser(user)) {
				us.saveUser(user, password);
			} else {
				us.saveUser(user, null);
				
				if (!password.equals("") && Context.hasPrivilege(PrivilegeConstants.EDIT_USER_PASSWORDS)) {
					if (log.isDebugEnabled())
						log.debug("calling changePassword for user " + user + " by user " + Context.getAuthenticatedUser());
					us.changePassword(user, password);
				}
			}
			
			if (StringUtils.hasLength(secretQuestion) && StringUtils.hasLength(secretAnswer)) {
				us.changeQuestionAnswer(user, secretQuestion, secretAnswer);
			}
			
			httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.saved");
		}
		return "redirect:users.list";
	}
	
	/**
	 * Superficially determines if this form is being filled out for a new user (basically just
	 * looks for a primary key (user_id)
	 * 
	 * @param user
	 * @return true/false if this user is new
	 */
	private Boolean isNewUser(User user) {
		return user == null ? true : user.getUserId() == null;
	}
	
}
